#pragma once

#include "HScrollableBackground.h"

#include <cassert>
#include <cstdlib>

#include <SDL_rect.h>
#include <SDL_render.h>

#include "main/MainConstants.h"
#include "utils/Error.h"

namespace {
    // Returns true if there are not
    // null textures.
    // Used in assert's to avoid
    // performance hit in Release
    bool checkNullTextures(SDL_Texture* * const tex, const size_t numTex) {
        for (size_t i = 0; i < numTex; ++i) {
            if (!tex[i]) {
                return false;
            }
        }

        return true;
    }

    // Returns true if all textures
    // have the same dimension
    bool checkTexturesDimensions(SDL_Texture* * const tex, const size_t numTex) {
        // Extract first texture dimensions
        int w;
        int h;
        const int result = SDL_QueryTexture(tex[0], 0, 0, &w, &h);
        if (result != 0) {
            printError("SDL_QueryTexture failed");
            assert(false);
        }

        for (size_t i = 1; i < numTex; ++i) {
            int tmpW;
            int tmpH;
            const int result = SDL_QueryTexture(tex[0], 0, 0, &tmpW, &tmpH);
            if (result != 0) {
                printError("SDL_QueryTexture failed");
                assert(false);
            }

            if (tmpW != w || tmpH != h) {
                return false;
            }
        }

        return true;
    }
}

HScrollableBackground::HScrollableBackground(SDL_Texture* * const tex,
                                             const size_t numTex,
                                             const uint32_t speed, 
                                             const HScrollDirection dir,
                                             const bool endless) 
                                             : mTextures(tex)
                                             , mNumTex(numTex)
                                             , mDir(dir)
                                             , mEndless(endless)
{
    // At least 1 texture
    assert(mNumTex > 0);

    // No textures should be nullptr
    assert(checkNullTextures(mTextures, mNumTex));

    // Textures should have the same size
    assert(checkTexturesDimensions(mTextures, mNumTex));

    // Compute first texture dimensions
    int w;
    int h;
    const int result = SDL_QueryTexture(mTextures[0], 0, 0, &w, &h);
    if (result != 0) {
        printError("SDL_QueryTexture failed");
        assert(false);
    }

    // Texture width == N * window width
    assert(w % WINDOW_WIDTH == 0);

    // Texture height == window height
    assert(h >= WINDOW_HEIGHT);

    // Cache background width, i.e., sum
    // of all textures width
    mBackgroundW = static_cast<uint32_t> (w) * mNumTex;

    // Cache texture width
    mTexWidth = static_cast<uint32_t> (w);

    // Initialize portion of the background and
    // texture to be shown based on scrolling direction
    SDL_Rect rect;
    rect.y = 0;
    rect.w = WINDOW_WIDTH;
    rect.h = WINDOW_HEIGHT;

    switch (dir) {
    case HScrollDirection::LEFT:
        rect.x = 0;
        mCurX = 0;
        mSpeed = speed;
        mTexData.mTexture = mTextures[0];
        break;

    case HScrollDirection::RIGHT:
        rect.x = mBackgroundW - WINDOW_WIDTH;
        mCurX = mBackgroundW - WINDOW_WIDTH;
        mSpeed = -speed;
        mTexData.mTexture = mTextures[mNumTex - 1];
        break; 

    default:
        break;
    }

    mTexData.updateRect(rect);
}    

void reset(HScrollableBackground& background) {
    assert(background.mTexData.mRect);

    SDL_Rect rect;
    rect.y = 0;
    rect.w = WINDOW_WIDTH;
    rect.h = WINDOW_HEIGHT;

    switch (background.mDir) {
    case HScrollDirection::LEFT:
        rect.x = 0;
        background.mCurX = 0;
        break;

    case HScrollDirection::RIGHT:
        rect.x = background.mBackgroundW - WINDOW_WIDTH;
        background.mCurX = background.mBackgroundW - WINDOW_WIDTH;
        break; 

    default:
        break;
    }

    background.mTexData.updateRect(rect);
}

void changeSpeed(const uint32_t speed, HScrollableBackground& background) {
    switch (background.mDir) {
    case HScrollDirection::LEFT:
        background.mSpeed = speed;
        break;
    case HScrollDirection::RIGHT:
        background.mSpeed = -speed;
        break;
    default:
        break;
    }
}

void changeDirection(const HScrollDirection dir, HScrollableBackground& background) {
    background.mDir = dir;
    switch (dir) {
    case HScrollDirection::LEFT:
        background.mSpeed = abs(background.mSpeed);
        break;
    case HScrollDirection::RIGHT:
        background.mSpeed = -abs(background.mSpeed);
        break;
    default:
        break;
    }
}

void setEndless(const bool b, HScrollableBackground& background) {
    background.mEndless = b;
}

void update(HScrollableBackground& background) {
    assert(background.mTexData.mRect);

    // Update current x coord
    background.mCurX += background.mSpeed;

    if (background.mEndless) {     
        if (background.mCurX < 0) {
            // Right scrolling case
            background.mCurX = background.mBackgroundW - WINDOW_WIDTH;
        } else if (background.mCurX > background.mBackgroundW - WINDOW_WIDTH) {
            // Left scrolling case
            background.mCurX = 0;
        }
    } else {                
        if (background.mCurX < 0) {
            // Right scrolling case
            background.mCurX = 0;
        } else if (background.mCurX > background.mBackgroundW - WINDOW_WIDTH) {
            // Left scrolling case
            background.mCurX = background.mBackgroundW - WINDOW_WIDTH;
        }
    }

    // Compute texture index
    const uint32_t texIndex = background.mCurX / background.mTexWidth;

    // Compute local texture x coord
    const uint32_t localX = background.mCurX % background.mTexWidth;

    // Update texture and rectangle to be shown
    background.mTexData.mTexture = background.mTextures[texIndex];
    background.mTexData.mRect->x = localX;
}

void update(const int speed, HScrollableBackground& background) {
    assert(background.mTexData.mRect);

    // Update current x coord
    background.mCurX += speed;

    if (background.mEndless) {     
        if (background.mCurX < 0) {
            // Right scrolling case
            background.mCurX = background.mBackgroundW - WINDOW_WIDTH;
        } else if (background.mCurX > background.mBackgroundW - WINDOW_WIDTH) {
            // Left scrolling case
            background.mCurX = 0;
        }
    } else {                
        if (background.mCurX < 0) {
            // Right scrolling case
            background.mCurX = 0;
        } else if (background.mCurX > background.mBackgroundW - WINDOW_WIDTH) {
            // Left scrolling case
            background.mCurX = background.mBackgroundW - WINDOW_WIDTH;
        }
    }

    // Compute texture index
    const uint32_t texIndex = background.mCurX / background.mTexWidth;

    // Compute local texture x coord
    const uint32_t localX = background.mCurX % background.mTexWidth;

    // Update texture and rectangle to be shown
    background.mTexData.mTexture = background.mTextures[texIndex];
    background.mTexData.mRect->x = localX;
}